# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import math
from datetime import timedelta
from zoneinfo import ZoneInfo
from bs4 import BeautifulSoup
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import AccessError, UserError, ValidationError
import requests
import json
from decimal import Decimal, getcontext, ROUND_HALF_UP

getcontext().prec = 16
_logger = logging.getLogger(__name__)


class SaleOrder(models.Model):
    _inherit = 'sale.order'

    order_qty = fields.Integer(compute='_compute_order_qty')
    allocated_material_qty = fields.Float(compute='_compute_allocated_material_qty', string="In Production Qty")
    product_template_ids = fields.Many2many('product.template', string="Product Template",
                                            compute='_compute_product_template_ids', readonly=True)

    customer_code = fields.Char(string='Customer Code', related='partner_id.code')
    mrp_production_first = fields.Many2one("mrp.production", compute='_compute_mrp_production_first', string="MRP")
    qty_delivered = fields.Float(string="Delivered Qty", compute='_compute_qty_delivered')
    qty_undelivered = fields.Float(string="Undelivered Qty", compute='_compute_qty_undelivered')
    s_order_suspension = fields.Boolean(string='Order suspension', default=False, copy=False, readonly=True)
    s_product_metre = fields.Float(string='Order Metre', compute="_compute_product_metre", store=True,
                                   compute_sudo=True, readonly=False)
    s_materiel_status = fields.Selection([
        ('S', 'NPI'),
        ('P', 'MP'),
        ('T', 'Test'),
        ('Q', 'QTA+NPI'),
    ], string='Product Status', compute='_compute_materiel_status', store=True)

    # related = 'first_line.s_materiel_status'
    @api.depends('order_line.s_materiel_status', 'order_line.s_qt')
    def _compute_materiel_status(self):
        for rec in self:
            rec.s_materiel_status = 'Q' if rec.first_line.s_materiel_status == 'S' and rec.first_line.s_qt else rec.first_line.s_materiel_status

    def action_suspend_order(self):
        self.mapped('mrp_production_ids').write({'s_order_suspension': True})
        self.write({'s_order_suspension': True})

    def action_cancel_suspend_order(self):
        self.mapped('mrp_production_ids').write({'s_order_suspension': False})
        self.write({'s_order_suspension': False})

    def _action_cancel(self):
        result = super()._action_cancel()
        for sale in self:
            for line in sale.order_line:
                for production in line.s_mrp_production_ids:
                    if production.state != 'cancel':
                        raise UserError('The production order is in progress and cannot be cancelled!')

                    if production.state != 'cancel':
                        production.with_user(SUPERUSER_ID).action_cancel()
        return result

    def _compute_qty_undelivered(self):
        for order in self:
            order.qty_undelivered = order.order_qty - order.qty_delivered

    def _compute_qty_delivered(self):
        for item in self:
            item.qty_delivered = sum(item.order_line.mapped('qty_delivered'))

    @api.depends('mrp_production_ids')
    def _compute_mrp_production_first(self):
        for item in self:
            if item.mrp_production_ids:
                item.mrp_production_first = item.mrp_production_ids[:1]
            else:
                item.mrp_production_first = None

    def _compute_product_template_ids(self):
        for item in self:
            if item.order_line:
                item.product_template_ids = (item.order_line.mapped('product_template_id')[0])
            else:
                item.product_template_ids = None

    def action_select_sale_to_wizard(self):
        self.ensure_one()
        order_line = self.order_line.filtered(lambda x: x.product_id.detailed_type == 'product')
        if not order_line:
            raise UserError('No unplanned sales orders')
        return {
            'name': _('Planned distribution of materials'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'plan.material.requirement.wizard',
            'target': 'new',
            'context': {
                'default_sale_id': self.id,
                'default_sale_order_line': order_line[0].id
            },
        }

    def _compute_allocated_material_qty(self):
        for order in self:
            order.allocated_material_qty = sum(order.product_template_ids.mapped('incoming_qty'))

    def _compute_order_qty(self):
        for order in self:
            order.order_qty = sum(
                order.order_line.filtered(lambda x: x.product_id.detailed_type != 'service').mapped('product_uom_qty'))

    @api.depends('procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids', 'order_line.s_mrp_production_ids')
    def _compute_mrp_production_ids(self):
        super()._compute_mrp_production_ids()
        for sale in self:
            sale.mrp_production_ids = sale.mrp_production_ids + sale.order_line.mapped('s_mrp_production_ids')
            sale.mrp_production_count = len(sale.mrp_production_ids)

    @api.depends('sale_workflow_state')
    def _compute_product_metre(self):
        PRECISION = 10
        for rec in self:
            for o_row in rec.first_line:
                # 將輸入值都轉為 Decimal（避免浮點誤差）
                try:
                    setl = Decimal(str(
                        o_row.product_template_id.uom_value_ids.filtered(lambda x: x.uom_id.code == 'SETL').uom_value or 1.0
                    ))
                    setw = Decimal(str(
                        o_row.product_template_id.uom_value_ids.filtered(lambda x: x.uom_id.code == 'SETW').uom_value or 1.0
                    ))
                    set_val = Decimal(str(
                        o_row.product_template_id.uom_value_ids.filtered(lambda x: x.uom_id.code == 'SET').uom_value or 1.0
                    ))
                    qty = Decimal(str(o_row.product_uom_qty or 0.0))
                except Exception:
                    rec.s_product_metre = 0.0
                    break
                if set_val == 0:
                    rec.s_product_metre = 0.0
                    break
                raw_spcs = (setw * setl / Decimal('1000000')) / set_val
                raw_total = raw_spcs * qty
                total_value = raw_total.quantize(Decimal('1.' + '0' * PRECISION), rounding=ROUND_HALF_UP)
                rec.s_product_metre = float(total_value)

    def action_cancel_plan(self):
        for sale in self:
            for mo in sale.mrp_production_ids:
                mo.unlink_plan()

    def action_order_jmi_post(self):
        """
        http://192.168.31.192:8980/js/a/login?__login=true&__ajax=json&username=b2Rvbw==&password=MTIzNDU2&validCode=
        http://192.168.31.14:8980/js/a/login?__login=true&__ajax=json&username=b2Rvbw==&password=MTIzNDU2&validCode=
        """
        opportunity_id = self.opportunity_id
        session_url = self.env['ir.config_parameter'].sudo().get_param('jmi_session_url')
        post_url = self.env['ir.config_parameter'].sudo().get_param('jmi_base_url')
        r_session_url = self.env['ir.config_parameter'].sudo().get_param('jmi_r_session_url')  # 返单
        return_url = self.env['ir.config_parameter'].sudo().get_param('jmi_return_url')  # 返单
        if not session_url or not post_url:
            raise UserError(_("Please configure JMI URL"))
        order_line = self.order_line.filtered(lambda x: x.product_id.detailed_type == 'product')
        for line in order_line:
            product = line.product_id.product_tmpl_id
            app = product.application_product_id.name
            commitment_date = ''
            # 将日期转换为时间戳
            if self.commitment_date:
                dt_with_tz = self.commitment_date.replace(tzinfo=ZoneInfo("Asia/Shanghai")) + timedelta(hours=8)
                commitment_date = dt_with_tz.isoformat()
            create_date = self.create_date.replace(tzinfo=ZoneInfo("Asia/Shanghai")) + timedelta(hours=8)
            self._compute_product_metre()
            notes = ''
            if self.note:
                soup = BeautifulSoup(str(self.note), "html.parser")
                notes = soup.get_text()
            if self.order_type == 'new':
                order_type = '1'
            elif self.order_type == 'repeat':  # 返单
                setl = self.env.ref('pcbmanufact.uom_set_length')
                setw = self.env.ref('pcbmanufact.uom_set_width')
                pnll = self.env.ref('pcbmanufact.uom_panel_length')
                pnlw = self.env.ref('pcbmanufact.uom_panel_width')
                set_length = product.uom_value_ids.filtered(lambda x: x.uom_id.id == setl.id).uom_value
                set_width = product.uom_value_ids.filtered(lambda x: x.uom_id.id == setw.id).uom_value
                shetl = product.uom_value_ids.filtered(lambda x: str(x.uom_id.name).upper() == 'SHETL').uom_value
                shetw = product.uom_value_ids.filtered(lambda x: str(x.uom_id.name).upper() == 'SHETW').uom_value
                pnl_length = product.uom_value_ids.filtered(lambda x: x.uom_id.id == pnll.id).uom_value
                pnl_width = product.uom_value_ids.filtered(lambda x: x.uom_id.id == pnlw.id).uom_value
                data = {
                    "moName": self.name,
                    "jobName": product.default_code,
                    "orderType": "返单",
                    "customer": self.partner_id.code,
                    "area": str(self.s_product_metre),
                    "pcsNumber": line.product_uom_qty,
                    "schDate": commitment_date,
                    "remarks": notes,
                    "testType": "夹具测试" if self.s_product_metre > 10 else '飞针测试',
                    "createDate": create_date.isoformat(),
                    "isOtherVersion": "1",
                    "setX": set_length,
                    "setY": set_width,
                    "panelX": pnl_length or '0',
                    "panelY": pnl_width or '0',
                    "shetlX": shetl,
                    "shetlY": shetw,
                    "kaiLiaoLiYongLv": opportunity_id.utilization_rate or '0',
                    "layerNumber": opportunity_id.layer or '0',
                    "setSheet": '0',
                }
                record_data = {
                    'doc_type': 'jmi-sale-return',
                    'api_params': data,
                    'api_route': return_url,
                    'state': 'success'
                }
                res_session = requests.post(r_session_url)
                content = json.loads(res_session.content)
                if content.get('result') != 'true':
                    _logger.info('JMI 登录失败: %s' % content)
                    return {'code': 400, 'message': 'JMI 登录失败'}
                sessionid = content.get('sessionid')
                headers = {'Content-Type': 'application/json'}
                res = requests.post(return_url + f'?__sid={sessionid}', json=data, headers=headers)
                result = json.loads(res.content)
                if not result.get('success'):
                    return {'code': 400, 'message': result.get('errorMsg')}
                record_data['result_json'] = res.content
                self.env['api.records'].create(record_data)
                return {'code': 200, 'message': '提交JMI返单成功'}
            else:
                order_type = '6'
            data = {
                "jobName": product.default_code,
                "countryName": self.partner_id.country_id.name,
                "customer": self.partner_id.code,
                "customerPart": line.pn,
                "degree": "",
                "moName": self.name,
                "agileTemp": '1',
                "quotationMetal": opportunity_id.board_material,
                "layerNumber": opportunity_id.layer or '0',
                "deliveryTime": commitment_date,
                "groupName": self.user_id.name,
                "setX": '0',
                "setY": '0',
                "pcsX": opportunity_id.unit_size_long or '0',
                "pcsY": opportunity_id.unit_size_width or '0',
                "orderPcs": '0',
                "orderArea": str(self.s_product_metre),  # 面积
                "point": "2",
                "remarks": self.state,
                "jiaji": notes,
                "orderType": order_type,
                "nandu": product_nandu.get(app, '1')
            }
            record_data = {
                'doc_type': 'jmi-sale',
                'api_params': data,
                'api_route': post_url,
            }
            try:
                record_data['state'] = 'success'
                record_data['api_route'] = session_url
                res_session = requests.post(session_url)
                content = json.loads(res_session.content)
                if content.get('result') != 'true':
                    _logger.info('JMI 登录失败: %s' % content)
                    return {'code': 400, 'message': 'JMI 登录失败'}
                sessionid = content.get('sessionid')
                record_data['result_json'] = res_session.content
                self.env['api.records'].create(record_data)
                headers = {'Content-Type': 'application/json'}
                res = requests.post(post_url + f'?__sid={sessionid}', json=data, headers=headers)
                result = json.loads(res.content)
                if not result.get('success'):
                    _logger.info('提交错误: %s' % result)
                    return {'code': 400, 'message': result.get('errorMsg')}
                record_data['api_route'] = post_url
                record_data['result_json'] = res.content
                self.env['api.records'].create(record_data)
                return {'code': 200, 'message': '提交JMI订单成功'}
            except Exception as e:
                record_data['state'] = 'fail'
                record_data['api_route'] = post_url
                record_data['result_json'] = _("提交错误: %s") % str(e)
                self.env['api.records'].create(record_data)
                raise UserError(_("提交错误: %s") % str(e))


product_nandu = {
    '常规板': '1',
    '光通信HDI': '8',
    '二次电源': '2',
    '引线框': '37',
    '软板': '11',
    '考试板': '39',
    'MPS': '36',
    'HDI-1阶': '14',
    '陶瓷板': '26',
    '铜基板': '25',
    '埋铜': '29',
    '铂金板': '32',
    'MEMS常规': '22',
    '三阶HDI板': '17',
    '阻抗板': '18',
    '电源板': '19',
    '平面变压器板': '21',
    '二次电源板': '22',
    '有BGA': '24',
    '急单': '25',
    '军品': '26',
    '医疗板': '27',
    '其它类': '28',
    '适配器': '3',
    '线圈板': '4',
    '天线板': '5',
    '高速板': '6',
    '光通信': '7',
    '光通信软硬结合': '9',
    '光通信软硬结合HDI': '10',
    '软硬结合': '17',
    '软硬结合HDI': '13',
    'HDI-2阶': '15',
    'HDI-3阶': '16',
    'Anylayer': '17',
    'MiniLED': '18',
    'MiniLED-HDI': '19',
    '机械盲埋孔': '20',
    '高层板(≥14)': '21',
    'MEMS-BCBR': '23',
    '铝基板': '24',
    '混压板': '27',
    '阶梯板': '28',
    '凸铜': '30',
    '埋器件': '31',
    '局部电金': '33',
    '背板': '34',
    '毫米波': '35',
    'IGBT': '38',
    '三次电源': '40',
    '连接器': '40',
    '铝基板(无压合)': '41',
    '铝基板(有压合)': '42'
}


class SaleOrderLine(models.Model):
    _inherit = 'sale.order.line'

    s_partner_id = fields.Many2one('res.partner', string='Customer', related='order_id.partner_id')
    s_normal_planned_qty = fields.Float(
        string='normal planned quantity', compute='compute_s_normal_planned_qty', store=True,
        readonly=True, copy=False
    )
    s_planned_qty = fields.Float(
        string='Planned quantity', compute='compute_s_remaining_planned_qty', store=True,
        readonly=True, copy=False
    )
    s_remaining_planned_qty = fields.Float(
        string='Remaining planned quantity',
        compute='compute_s_remaining_planned_qty', store=True, readonly=False,
        copy=False
    )
    s_mrp_production_ids = fields.Many2many('mrp.production', copy=False)
    s_planned_receiving_date = fields.Datetime(
        'Planned Receiving Date', copy=False,
        related='order_id.planned_receiving_date')
    s_note = fields.Html('Note', related='order_id.note')
    s_materiel_status = fields.Selection([
        ('S', 'NPI'),
        ('P', 'MP'),
        ('T', 'Test'),
        ('Q', 'QTA+NPI'),
    ], string='Product Status', tracking=True)
    s_poor_subcon_qty = fields.Float(string='Qty Subcon Poor', compute='_compute_s_poor_subcon_qty')
    s_poor_products = fields.Many2many('product.product', 's_poor_products', string='Poor Products')
    s_qt = fields.Boolean(string="QTA")
    s_plan_close = fields.Boolean(string='Plan close', default=False, copy=False, readonly=True)
    s_plan_note = fields.Text(string='Plan Note')

    def _compute_s_poor_subcon_qty(self):
        for rec in self:
            # 根据工单工序报废数量计算总报废数量、报废产品
            poor_workorders = self.env['mrp.workorder'].sudo().search([
                ('qty_op_reject', '>', 0),
                ('production_id', 'in', rec.order_id.mrp_production_ids.ids)
            ])
            qty_op_reject = sum(poor_workorders.mapped('qty_op_reject')) if poor_workorders else 0
            # 计算已补投数量
            supplement_productions = self.env['mrp.production'].search([
                ('origin', '=', rec.order_id.name),
                ('mo_type', '=', 'M'),
                ('state', '!=', 'cancel')
            ])
            supplement_qty = sum(supplement_productions.mapped('product_qty')) if supplement_productions else 0
            rec.s_poor_products = [(6, 0, poor_workorders.product_id.ids)]
            rec.s_poor_subcon_qty = qty_op_reject - supplement_qty

    @api.model
    def act_supplement_sale_order_line_select_to_wizard(self):
        lines = self.env['sale.order.line'].sudo().search([
            ('product_id.detailed_type', '=', 'product'),
            ('state', '=', 'sale')
        ]).filtered(lambda x: x.s_poor_subcon_qty > 0)
        action = {
            'name': _('Select Sale Order'),
            'view_mode': 'tree',
            'res_model': 'sale.order.line',
            'target': 'new',
            'type': 'ir.actions.act_window',
            'views': [(self.env.ref('pcbmanufact.sb_supplement_sale_order_line_select_to_wizard_tree').id, 'list')],
            'domain': [('id', 'in', lines.ids)],
            'context': {
                'mo_type': 'M'
            }
        }
        return action

    @api.depends('s_mrp_production_ids.state', 's_mrp_production_ids.product_uom_qty', 'product_uom_qty')
    def compute_s_remaining_planned_qty(self):
        for rec in self:
            if rec.product_id.detailed_type != 'product':
                rec.write({
                    's_planned_qty': 0,
                    's_remaining_planned_qty': 0
                })
                continue
            # 过滤掉状态为 'cancel' 的生产订单
            valid_productions = rec.s_mrp_production_ids.filtered(lambda x: x.state != 'cancel' and x.mo_type != 'C')
            many_production_ids = self.env['mrp.production']
            production_ids = self.env['mrp.production']
            remaining_planned_qty = 0
            for p in valid_productions:
                if p.mapped('s_sale_order_line_ids').filtered(lambda x: x.state != 'cancel') - rec:
                    many_production_ids |= p
                else:
                    production_ids |= p

            all_sale_line_ids = many_production_ids \
                .mapped('s_sale_order_line_ids') \
                .filtered(lambda x: x.product_uom_qty > 0) \
                .sorted(key=lambda x: x.id)

            # 计算总的计划数量
            total_planned_qty = sum(many_production_ids.mapped('product_uom_qty'))
            if total_planned_qty:
                total_sale_qty = sum(all_sale_line_ids.mapped('product_uom_qty'))
                remaining_planned_qty += math.ceil(total_planned_qty * (rec.product_uom_qty / total_sale_qty))
            remaining_planned_qty += sum(production_ids.mapped('product_uom_qty'))
            rec.s_planned_qty = remaining_planned_qty
            rec.s_remaining_planned_qty = max(rec.product_uom_qty - rec.s_planned_qty, 0)

    @api.depends('s_mrp_production_ids.state', 's_mrp_production_ids.product_uom_qty', 'product_uom_qty')
    def compute_s_normal_planned_qty(self):
        for rec in self:
            if rec.product_id.detailed_type != 'product':
                rec.s_normal_planned_qty = 0
                continue
            # 过滤掉状态为 'cancel' 的生产订单
            valid_productions = rec.s_mrp_production_ids.filtered(lambda x: x.state != 'cancel' and x.mo_type == 'N')
            many_production_ids = self.env['mrp.production']
            production_ids = self.env['mrp.production']
            remaining_planned_qty = 0
            for p in valid_productions:
                if p.mapped('s_sale_order_line_ids').filtered(lambda x: x.state != 'cancel') - rec:
                    many_production_ids |= p
                else:
                    production_ids |= p

            all_sale_line_ids = many_production_ids \
                .mapped('s_sale_order_line_ids') \
                .filtered(lambda x: x.product_uom_qty > 0) \
                .sorted(key=lambda x: x.id)

            # 计算总的计划数量
            total_planned_qty = sum(many_production_ids.mapped('product_uom_qty'))
            if total_planned_qty:
                total_sale_qty = sum(all_sale_line_ids.mapped('product_uom_qty'))
                remaining_planned_qty += math.ceil(total_planned_qty * (rec.product_uom_qty / total_sale_qty))
            remaining_planned_qty += sum(production_ids.mapped('product_uom_qty'))
            rec.s_normal_planned_qty = remaining_planned_qty

    def action_select_sale_to_wizard(self):
        for rec in self:
            if rec.product_id.detailed_type != 'product':
                raise UserError(_('The product is not allowed to be produced!'))

        if len(self.mapped('product_id')) > 1:
            raise UserError(_('Different products are not allowed to be combined for feeding!'))

        # 检测是否有高版本的产品，如果有需要先升级销售单产品之后才可投料
        product_id = self[0].product_id
        default_code = product_id.default_code
        default_code = default_code.split('-')[0]
        i = default_code[-1:]
        if i.isdigit():
            i = int(i)
            code = default_code[:-1] + f'{i + 1}'
            if self.env['product.product'].search([
                ('default_code', '=', code),
                ('s_disable_down_card', '=', False)
            ], limit=1):
                raise UserError(
                    _('This product (%s) has a new version. Please notify the engineering team to go through the ECR application process and change to the latest version before feeding',
                      product_id.default_code))

        mo_type = self._context.get('mo_type', '')
        action = {
            'name': _('Planned distribution of materials'),
            'type': 'ir.actions.act_window',
            'view_mode': 'form',
            'res_model': 'plan.material.requirement.wizard',
            'target': 'new',
        }
        if mo_type == 'M':
            action.update({
                'res_model': 's.supp.plan.material.requirement.wizard',
                'context': self._context
            })
        return action

    def action_plan_close(self):
        self.write({'s_plan_close': True})
        return self.env["ir.actions.actions"]._for_xml_id("pcbmanufact.action_sale_order_line_select_to_wizard")
